package com.ling.pl.security.utils;


import com.ling.pl.core.cache.ApplicationCache;
import com.ling.pl.core.commons.exception.AccessDeniedException;
import com.ling.pl.core.commons.utils.SpringUtil;
import com.ling.pl.security.model.DefaultRole;
import com.ling.pl.security.model.DefaultUrl;
import com.ling.pl.security.model.DefaultUser;
import com.ling.pl.security.model.RoleMember;
import com.ling.pl.security.service.DefaultRoleService;
import com.ling.pl.security.service.DefaultUrlService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @author bo.wang
 */
@Component(UrlMetadataSource.BEAN_ID)
public class UrlMetadataSource {
    public static final String BEAN_ID = "ling2.urlMetadataSource";
    Collection<AnonymousUrl> safeUrls = null;
    @Resource
    private DefaultRoleService roleService;
    @Resource
    private DefaultUrlService urlService;
    @Resource
    private ApplicationCache applicationCache;
    @Value("${ling2.useConservativeAuthorityStrategy:true}")
    private boolean useConservativeAuthorityStrategy;
    private String urlMetadataCacheKey = "url_metadata_";
    private Map<String, SecurityConfigAttribute> anonymousUrlMetadata = new HashMap<String, SecurityConfigAttribute>();
    private AntPathMatcher matcher = new AntPathMatcher();

    @SuppressWarnings("unchecked")
    public Collection<ConfigAttribute> getAttributes(String url, DefaultUser authentication) throws IllegalArgumentException {
        Collection<ConfigAttribute> safeUrlAttributes = this.getAnonymousUrlAttributes(url);
        if (safeUrlAttributes != null) {
//			System.out.println(url+" safeUrlAttributes"+"size:"+safeUrlAttributes.size());
            return safeUrlAttributes;
        }
        String orgCompanyId = authentication.getCompanyId();
        Assert.hasText(orgCompanyId, "current login user[" + authentication.getUsername() + "] is not specified company ID");
        Map<String, Map<String, List<ConfigAttribute>>> metaData = loadMetaData();
        Map<String, List<ConfigAttribute>> map = null;
        String[] companyIds = orgCompanyId.split(",");
        for (String companyId : companyIds) {
            map = metaData.get(companyId);
            if (map != null) break;
        }
        if (map == null) {
            if (!useConservativeAuthorityStrategy || authentication.isAdministrator()) {
                return CollectionUtils.EMPTY_COLLECTION;
            } else {
                throw new AccessDeniedException("Access denied");
            }
        }
//		System.out.println( "process:"+url);
        if (!map.containsKey(url)) {
//			System.out.println( "!containsKey:"+url);
            url = url.substring(1, url.length());
            if (!map.containsKey(url)) {
                if (!useConservativeAuthorityStrategy || authentication.isAdministrator()) {
                    return CollectionUtils.EMPTY_COLLECTION;
                } else {
                    throw new AccessDeniedException("Access denied");
                }
            } else {
                List<ConfigAttribute> attributes = map.get(url);
                if (attributes.size() > 0) {
                    return attributes;
                } else {
                    if (!useConservativeAuthorityStrategy || authentication.isAdministrator()) {
                        return attributes;
                    } else {
                        throw new AccessDeniedException("Access denied");
                    }
                }
            }
        } else {
//			System.out.println( "containsKey:"+url);
            List<ConfigAttribute> attributes = map.get(url);
//			System.out.println( "containsKey and attributes size:"+attributes.size());
            if (attributes.size() > 0) {
                return attributes;
            } else {
                if (!useConservativeAuthorityStrategy || authentication.isAdministrator()) {
                    return attributes;
                } else {
                    throw new AccessDeniedException("Access denied");
                }
            }
        }
    }

    private Collection<ConfigAttribute> getAnonymousUrlAttributes(String url) {
        Collection<ConfigAttribute> attributes = null;
        for (String patternUrl : getAnonymousUrlMetadata().keySet()) {
            if (matcher.match(patternUrl, url)) {
                attributes = new ArrayList<ConfigAttribute>();
                attributes.add(getAnonymousUrlMetadata().get(patternUrl));
                break;
            }
        }
        return attributes;
    }

    @SuppressWarnings("unchecked")
    public Collection<SecurityConfigAttribute> getAllSecurityConfigAttributes() {
        return CollectionUtils.EMPTY_COLLECTION;
    }

    @SuppressWarnings("unchecked")
    private Map<String, Map<String, List<ConfigAttribute>>> loadMetaData() {
        Map<String, Map<String, List<ConfigAttribute>>> metaData = (Map<String, Map<String, List<ConfigAttribute>>>) applicationCache.getCacheObject(urlMetadataCacheKey);
        if (metaData == null) {
            initUrlMetaData();
            metaData = (Map<String, Map<String, List<ConfigAttribute>>>) applicationCache.getCacheObject(urlMetadataCacheKey);
        }
        return metaData;
    }

    public void initUrlMetaData() {
        Map<String, Map<String, List<SecurityConfigAttribute>>> metaData = new HashMap<String, Map<String, List<SecurityConfigAttribute>>>();
        for (DefaultRole role : roleService.loadAllRoles()) {
            role.setRoleMembers(roleService.loadRoleMemberByRoleId(role.getId()));
            role.setUrls(urlService.loadUrlsByRoleId(role.getId()));
            String companyId = role.getCompanyId();
            Map<String, List<SecurityConfigAttribute>> mapAttribute = metaData.get(companyId);
            if (mapAttribute == null) {
                mapAttribute = new HashMap<String, List<SecurityConfigAttribute>>();
                metaData.put(companyId, mapAttribute);
            }
            for (DefaultUrl url : role.getUrls()) {
                String targetUrl = url.getUrl();
                if (StringUtils.isEmpty(targetUrl)) {
                    targetUrl = url.getName();
                }
                if (StringUtils.isEmpty(targetUrl)) {
                    continue;
                }
                targetUrl = processUrl(targetUrl);
                List<SecurityConfigAttribute> attributes = mapAttribute.get(targetUrl);
                if (attributes == null) {
                    attributes = new ArrayList<SecurityConfigAttribute>();
                    mapAttribute.put(targetUrl, attributes);
                }
                this.buildSecurityConfigAttributes(role, attributes);
                //System.out.println("initUrlMetaData:"+targetUrl+" attributes size "+attributes.size());
            }
        }
        applicationCache.putCacheObject(urlMetadataCacheKey, metaData);
    }

    private String getRequestPath(HttpServletRequest request) {
        String url = request.getServletPath();
        if (request.getPathInfo() != null) {
            url += request.getPathInfo();
        }
        return url;
    }

    private void buildSecurityConfigAttributes(DefaultRole role, List<SecurityConfigAttribute> list) {
        for (RoleMember member : role.getRoleMembers()) {
            SecurityConfigAttribute attribute = null;
            if (member.getUser() != null) {
                attribute = new SecurityConfigAttribute(AttributeType.user, member.isGranted(), role.getCompanyId());
                attribute.setMember(member.getUser());
            }
            if (member.getOrg() != null) {
                attribute = new SecurityConfigAttribute(AttributeType.org, member.isGranted(), role.getCompanyId());
                attribute.setMember(member.getOrg());
            }
            if (member.getPosition() != null) {
                attribute = new SecurityConfigAttribute(AttributeType.position, member.isGranted(), role.getCompanyId());
                attribute.setMember(member.getPosition());
            }
            if (member.getGroup() != null) {
                attribute = new SecurityConfigAttribute(AttributeType.group, member.isGranted(), role.getCompanyId());
                attribute.setMember(member.getGroup());
            }
            if (attribute != null) {
                list.add(attribute);
            }
        }
    }

    private String processUrl(String targetUrl) {
        targetUrl = targetUrl.trim();
        return targetUrl;
    }

    private void buildSafeUrlSecurityConfigAttributes(Collection<AnonymousUrl> safeUrls) {
        for (AnonymousUrl url : safeUrls) {
            String pattern = url.getUrlPattern();
            SecurityConfigAttribute attribute = new SecurityConfigAttribute(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY);
            anonymousUrlMetadata.put(pattern, attribute);
        }

    }

    public Map<String, SecurityConfigAttribute> getAnonymousUrlMetadata() {
        if (safeUrls == null) {
            safeUrls = SpringUtil.getContext().getBeansOfType(AnonymousUrl.class).values();
            buildSafeUrlSecurityConfigAttributes(safeUrls);
        }
        return anonymousUrlMetadata;
    }

    public boolean isUseConservativeAuthorityStrategy() {
        return useConservativeAuthorityStrategy;
    }

    public void setUseConservativeAuthorityStrategy(
            boolean useConservativeAuthorityStrategy) {
        this.useConservativeAuthorityStrategy = useConservativeAuthorityStrategy;
    }
}